package com.leyou.auth.service;

import com.leyou.auth.config.JwtProperties;
import com.leyou.common.auth.entity.AppInfo;
import com.leyou.auth.entity.ApplicationInfo;
import com.leyou.auth.mapper.ApplicationInfoMapper;
import com.leyou.common.auth.entity.Payload;
import com.leyou.common.auth.entity.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.UserDTO;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class AuthService {

    @Autowired
    private JwtProperties prop;  //jwt相关属性

    @Autowired
    private UserClient userClient;   //用户服务

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ApplicationInfoMapper appMapper;  //访问数据库

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;   //密码加密



    private static final String USER_ROLE = "role_user";  //用户角色


    /**
     * 为用户授权，将token信息封装在cookie中返回给客户端
     *
     * @param username
     * @param password
     * @param response
     */
    public void login(String username, String password, HttpServletResponse response) {
        try {
            //查询用户
            UserDTO user = userClient.queryUserByUsernameAndPassword(username, password);

            //封装用户信息， 没写权限功能
            UserInfo userInfo = new UserInfo(user.getId(), user.getUsername(), USER_ROLE);

            //生成 token                                          用户信息         私钥              过期时间
            String token = JwtUtils.generateTokenExpireInMinutes(userInfo, prop.getPrivateKey(), prop.getUser().getExpire());

            CookieUtils.newBuilder().
                    response(response).
                    httpOnly(true). //防止xss跨站攻击  不允许js操作cookie
                    domain(prop.getUser().getCookieDomain()).     //cookie域 leyou.com
                    name(prop.getUser().getCookieName()).value(token).  //设置cookie名称 LY_TOKEN 与值
                    build();        //发送 cookie
        } catch (Exception e) {
            //授权失败
            throw new LyException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
        }

    }

    /**
     * 1、从request中取出token
     * 2、从token中解析出用户信息
     * 3、将用户信息响应回去
     *
     * @param request
     * @param response
     * @return
     */
    public UserInfo verifyUser(HttpServletRequest request, HttpServletResponse response) {

        try {
            String token = CookieUtils.getCookieValue(request, prop.getUser().getCookieName());

            Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserInfo.class);
            //检查此key是否存在
            Boolean flag = redisTemplate.hasKey(payload.getId());
            if (flag != null && flag) {
                throw new LyException(ExceptionEnum.UNAUTHORIZED);
            }

            //如果token剩余时间小于15分钟，我们就生成一个新的token给前端
            //获取过期时间  如果生成token的时间是 08:32:00 那么过期时间就是09:02:00
            Date expirationTime = payload.getExpiration();
            //获取刷新时间  将过期时间减去15分钟 例如 08:47:00
            DateTime refreshTime = new DateTime(expirationTime.getTime()).minusMinutes(prop.getUser().getMinRefreshInterval());
            //如果刷新时间的毫秒值小于系统当前时间， 也就是说token还有不到15分钟就要过期了
            //那我们就生成一个新token
            if (refreshTime.isBefore(System.currentTimeMillis())) {
                //如果剩余时间小于15分钟

                //重新 生成 token                                          用户信息         私钥              过期时间
                token = JwtUtils.generateTokenExpireInMinutes(payload.getInfo(), prop.getPrivateKey(), prop.getUser().getExpire());

                CookieUtils.newBuilder().    //建造者模式
                        response(response).
                        httpOnly(true). //防止xss跨站攻击  不允许js操作cookie
                        domain(prop.getUser().getCookieDomain()).     //cookie域 leyou.com
                        name(prop.getUser().getCookieName()).value(token).  //设置cookie名称 LY_TOKEN 与值
                        build();        //发送 cookie

            }


            return payload.getInfo();
        } catch (Exception e) {
            // 抛出异常，证明token无效，直接返回401
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
    }

    /**
     * 用户登出
     * 将token记录在redis中，设置过期时间为30分钟
     * 用户再次使用token登录时，我们比对redis中是否有这个token，有的话就不让登陆
     *
     * @return
     */
    public void logout(HttpServletRequest request, HttpServletResponse response) {

        //获取token
        String token = CookieUtils.getCookieValue(request, prop.getUser().getCookieName());
        //解析token
        Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserInfo.class);
        //获取jwtId
        String id = payload.getId();

        long time = payload.getExpiration().getTime() - System.currentTimeMillis();

        //剩余时间大于5秒，才会纪录进redis中
        if (time > 5000) {
            redisTemplate.opsForValue().set(id, "", time, TimeUnit.MILLISECONDS);
        }
        // 删除cookie
        CookieUtils.deleteCookie(prop.getUser().getCookieName(),prop.getUser().getCookieDomain(), response);

    }

    /**
     * 微服务认证并申请令牌
     * @param id
     * @param secret
     * @return  返回 token
     */
    public String authenticate(Long id, String secret) {

        //根据服务id查询服务信息
        ApplicationInfo app = appMapper.selectByPrimaryKey(id);

        if (app == null){
            throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
        }

        if (!passwordEncoder.matches(secret,app.getSecret())){
            throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
        }

        List<Long> serviceIds = appMapper.queryTargetIdList(id);

        AppInfo appInfo = new AppInfo();
        appInfo.setId(id);
        appInfo.setServiceName(app.getServiceName());
        appInfo.setTargetList(serviceIds);

        return JwtUtils.generateTokenExpireInMinutes(appInfo,prop.getPrivateKey(),prop.getApp().getExpire());
    }
}
